<?php
/*
 * CKFinder
 * ========
 * http://ckfinder.com
 * Copyright (C) 2007-2011, CKSource - Frederico Knabben. All rights reserved.
 *
 * The software, this file and its contents are subject to the CKFinder
 * License. Please read the license.txt file before using, installing, copying,
 * modifying or distribute this file or part of its contents. The contents of
 * this file is part of the Source Code of CKFinder.
 */
if (!defined('IN_CKFINDER')) exit;

/**
 * @package CKFinder
 * @subpackage Core
 * @copyright CKSource - Frederico Knabben
 */
class CKFinder_Connector_Core_Hooks
{

	/**
	 * Run user defined hooks
	 *
	 * @param string $event
	 * @param object $errorHandler
	 * @param array $args
	 * @return boolean (true to continue processing, false otherwise)
	 */
	public static function run($event, $args = array())
	{
		$config = $GLOBALS['config'];
		if (!isset($config['Hooks'])) {
			return true;
		}
		$hooks =& $config['Hooks'];

		if (!is_array($hooks) || !array_key_exists($event, $hooks) || !is_array($hooks[$event])) {
			return true;
		}

		$errorHandler = $GLOBALS['connector']->getErrorHandler();

		foreach ($hooks[$event] as $i => $hook) {

			$object = NULL;
			$method = NULL;
			$function = NULL;
			$data = NULL;
			$passData = false;

			/* $hook can be: a function, an object, an array of $functiontion and $data,
			 * an array of just a function, an array of object and method, or an
			 * array of object, method, and data.
			 */
			//function
			if (is_string($hook)) {
				$function = $hook;
			}
			//object
			else if (is_object($hook)) {
				$object = $hooks[$event][$i];
				$method = "on" . $event;
			}
			//array of...
			else if (is_array($hook)) {
				$count = count($hook);
				if ($count) {
					//...object
					if (is_object($hook[0])) {
						$object = $hooks[$event][$i][0];
						if ($count < 2) {
							$method = "on" . $event;
						} else {
							//...object and method
							$method = $hook[1];
							if (count($hook) > 2) {
								//...object, method and data
								$passData = true;
								$data = $hook[2];
							}
						}
					}
					//...function
					else if (is_string($hook[0])) {
						$function = $hook[0];
						if ($count > 1) {
							//...function with data
							$passData = true;
							$data = $hook[1];
						}
					}
				}
			}

			/* If defined, add data to the arguments array */
			if ($passData) {
				$args = array_merge(array($data), $args);
			}

			if (isset($object)) {
				$callback = array($object, $method);
			}
			else if (false !== ($pos = strpos($function, '::'))) {
				$callback = array(substr($function, 0, $pos), substr($function, $pos + 2));
			}
			else {
				$callback = $function;
			}

			if (is_callable($callback)) {
				$ret = call_user_func_array($callback, $args);
			}
			else {
				$functionName = CKFinder_Connector_Core_Hooks::_printCallback($callback);
				$errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_CUSTOM_ERROR,
                "CKFinder failed to call a hook: " . $functionName);
				return false;
			}

			//String return is a custom error
			if (is_string($ret)) {
				$errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_CUSTOM_ERROR, $ret);
				return false;
			}
			//hook returned an error code, user error codes start from 50000
			//error codes are important because this way it is possible to create multilanguage extensions
			//TODO: two custom extensions may be popular and return the same error codes
			//recomendation: create a function that calculates the error codes starting number
			//for an extension, a pool of 100 error codes for each extension should be safe enough
			else if (is_int($ret)) {
				$errorHandler->throwError($ret);
				return false;
			}
			//no value returned
			else if( $ret === null ) {
				$functionName = CKFinder_Connector_Core_Hooks::_printCallback($callback);
				$errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_CUSTOM_ERROR,
                "CKFinder extension returned an invalid value (null)." .
                "Hook " . $functionName . " should return a value.");
				return false;
			}
			else if (!$ret) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Print user friendly name of a callback
	 *
	 * @param mixed $callback
	 * @return string
	 */
	public static function _printCallback($callback)
	{
		if (is_array($callback)) {
			if (is_object($callback[0])) {
				$className = get_class($callback[0]);
			} else {
				$className = strval($callback[0]);
			}
			$functionName = $className . '::' . strval($callback[1]);
		}
		else {
			$functionName = strval($callback);
		}
		return $functionName;
	}
}
